package march;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.List;

import javax.swing.JFrame;

import quads.DownQuad;
import quads.MarchQuad;

public class GridCanvas extends Canvas {

	
	/**
	 * 
	 */
	private static final long serialVersionUID = -8102735004100062353L;
	public int width;
	private static final int SPACING = 2;
	private static final int MAX_DIMENSION = 800;
	private static final int MOVE_BEATS = 2;
	
	private int height;
	
	private Image photoImage;
	private Image nextImage;
	
	private Image gridImage;
	private Image nextGridImage;
	private MarchableImage mi;
	private MarchableImage miOnDeck; // will be same width as mi
	private int rows;
	private int cols;
	private boolean[][] bools;
	//private boolean[][] transitionalBools;
	
	private JFrame frame;
	
	private Class<? extends MarchQuad> quadOnDeck;
	
	private List<MarchQuad> quads;
	private boolean transitioning = false;
	private int moveClock;
	private int transitionOffset;
	//private int transitionalHeight;
	
	public GridCanvas(List<MarchQuad> quads) {
		
		this.quads = quads;
		width = MAX_DIMENSION;
		height = MAX_DIMENSION;
		
		addMouseListener(new MouseListener() {
			
			@Override
			public void mouseReleased(MouseEvent e) {handleClick(e);}
			
			@Override
			public void mousePressed(MouseEvent e) {}
			
			@Override
			public void mouseExited(MouseEvent e) {}
			
			@Override
			public void mouseEntered(MouseEvent e) {}
			
			@Override
			public void mouseClicked(MouseEvent e) {}

			
		});
	}
	
	private void setImage(Image image) {
		this.photoImage = image;
		int w = image.getWidth(null);
		int h = image.getHeight(null);
		double ratio = w * 1.0 / h;
		
		if (w > h) {
			width = MAX_DIMENSION;
			height = (int) (width / ratio);
		} else {
			height = MAX_DIMENSION;
			width = (int) (height * ratio);
		}
		
		setBounds(0, 0, width, height);
		
		
		
	}
	
	public void setMarchableImage(MarchableImage mi) {
		this.mi = mi;
		
		bools = mi.getMarchImage();
		setImage(mi.getOriginalImage());
		
		
		
		rows = bools.length;
		cols = bools[0].length;
		
		createGridImage();
	}
	
	public void setMarchableImage(File f) throws IOException {
		MarchableImage mi = new MarchableImage(f);
		setMarchableImage(mi);
	}
	
	
	public void handlePulse() {
		if (transitioning) {
			moveClock--;
			
			if (moveClock <= 0) {
				
				moveClock = MOVE_BEATS;
				transitionOffset++;
				
				//int nextHeight = miOnDeck.getMarchImage().length * height / rows; // TODO this is not the height you are looking for.
				
				//transitionalHeight += (nextHeight - height) / rows;
				
				//setBounds(0, 0, width, transitionalHeight);
				
				//frame.pack();
				
				for (MarchQuad q : quads) {
					q.setTransitionCeiling(transitionOffset);
				}
				
				if (transitionOffset >= mi.getMarchImage().length) {
					completeTransition();
				}
				
				repaint();
				
			}
		}
	}
	
	
	private void completeTransition() {
		// TODO Auto-generated method stub
		System.out.println("Transition complete");
		transitioning = false;
		transitionOffset = 0;
		int quadJump = bools.length - miOnDeck.getMarchImage().length;
		setMarchableImage(miOnDeck);
		nextImage = null;
		nextGridImage = null;
		setBounds(0, 0, width, height);
		frame.pack();
		
		for (MarchQuad q : quads) {
			q.setBools(bools, quadJump);
		}
	}

	public void beginTransition() {
		
		beginTransition(miOnDeck);
	}
	
	public void beginTransition(MarchableImage newImage) {
		
		if (newImage == null) {
			System.out.println("Cannot transition to null image");
			return;
		}
		
		//transitionalHeight = height;
		
		nextImage = newImage.getOriginalImage();
		createNextGridImage();
		
		boolean[][] oldBools = mi.getMarchImage();
		boolean[][] newBools = newImage.getMarchImage();
		
		int rows = oldBools.length + newBools.length;
		
		boolean[][] transitionalBools = new boolean[rows][oldBools[0].length]; // new and old always have same num of cols
		
		for (int r = 0; r < oldBools.length; r++) {
			for (int c = 0; c < oldBools[0].length; c++) {
				transitionalBools[r][c] = oldBools[r][c];
			}
		}
		
		for (int r = 0; r < newBools.length; r++) {
			for (int c = 0; c < newBools[0].length; c++) {
				transitionalBools[r + oldBools.length][c] = newBools[r][c];
			}
		}
		
		bools = transitionalBools;
		
		for (MarchQuad q : quads) {
			q.setBools(transitionalBools, 0);
		}
		
		moveClock = MOVE_BEATS;
		transitionOffset = 0;
		transitioning  = true;
	}

	private void createGridImage() {
		this.gridImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
		Graphics2D gi = (Graphics2D) gridImage.getGraphics();
		drawGrid(gi, bools);
	}
	
	private void createNextGridImage() {
		
		int rowHeight = height / rows;
		int h = miOnDeck.getMarchImage().length * rowHeight;
		
		this.nextGridImage = new BufferedImage(width, h, BufferedImage.TYPE_4BYTE_ABGR);
		Graphics2D gi = (Graphics2D) nextGridImage.getGraphics();
		drawGrid(gi, miOnDeck.getMarchImage());
	}
	
	
	@Override
	public void paint(Graphics g) {
		
		if (rows <= 0) return; // prevent div by 0
		
		Graphics2D g2 = (Graphics2D) g;
		
		Image offscreen = createImage(width, height);
		Graphics2D bg = (Graphics2D) offscreen.getGraphics();
		
		bg.clearRect(0, 0, width, height);
		
		int rowHeight = height/rows;
		//int colWidth = width/cols;
		
		int oldOffset = - transitionOffset * rowHeight;
		int newOffset = height - transitionOffset * rowHeight;
		
		if (miOnDeck != null) {
			int newHeight = miOnDeck.getMarchImage().length * rowHeight;
			
			if (nextImage != null) {
				bg.drawImage(nextImage, 0, newOffset, width, newHeight, null);
			}
			
			if (nextGridImage != null) {
				bg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
				bg.drawImage(nextGridImage, 0, newOffset, width, newHeight, null);
			}
		}
		
		
		if (photoImage != null) {
			bg.drawImage(photoImage, 0, oldOffset, width, height, null);
		}
		
		
		
		if (gridImage != null) {
			bg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
			bg.drawImage(gridImage, 0, oldOffset, width, height, null);
		}
		
		
		
		if (!quads.isEmpty()) {
			drawQuads(bg);
		}
		
		g2.drawImage(offscreen, 0, 0, null);
		
	}

	private void drawGrid(Graphics2D bg, boolean[][] bools) {
		if (bools != null) {
			for (int r = 0; r < bools.length; r++) {
				for (int c = 0; c < bools[0].length; c++) {
					paintSquare(bg, r, c, bools[r][c]);
				}
			}
		}
	}

	private void drawQuads(Graphics2D bg) {
		
		bg.setColor(Color.black);
		bg.setStroke(new BasicStroke(SPACING * 2, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER));
		
	
		int w = (width * 2) / cols;
		int h = (height * 2) / rows;
		
		for (MarchQuad mq : quads) {
			
			int x = (mq.getCol() * width) / cols;
			int y = ((mq.getRow() - transitionOffset) * height) / rows;
			
			Rectangle r = new Rectangle(x, y, w, h);
			mq.paint(bg, r);
		}
	}
	
	

	public void update(Graphics g)
    {
         paint(g);
    } 
	
	
	public void paintSquare(Graphics2D g, int row, int col, boolean black) {
		
		if (black) {
			g.setColor(new Color(0, 0, 0, 100));
		} else {
			g.setColor(new Color(255, 255, 255, 100));
		}
		
		int x = col * width / cols + SPACING;
		int y = row * height / rows + SPACING;
		
		int w = width / cols - SPACING * 2;
		int h = height / rows - SPACING * 2;
		
		g.fill3DRect(x, y, w, h, true);
		
		
		
	}
	
	@SuppressWarnings("unchecked")
	public void launchQuad(int r, int c, String className) {
		MarchQuad mq = null;
		
		Class<? extends MarchQuad> cls;
		try {
			cls =  (Class<? extends MarchQuad>) Class.forName(className);
			Object[] args = new Object[1];
			args[0] = bools;
			
			mq = cls.getConstructor(boolean[][].class).newInstance(args);
			mq.setPosition(r, c);
			quads.add(mq);
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	public void launchQuad(int row, int col, MarchQuad quad) {
		
		quad.setBools(bools, 0);
		quad.setPosition(row, col);
		quads.add(quad);
		
	}

	private void handleClick(MouseEvent e) {
		int x = e.getX();
		int y = e.getY();
		
		int col = (x * cols) / width;
		int row = (y * rows) / height;
		
		if (e.getButton() == MouseEvent.BUTTON1) {
			// launch quad
			MarchQuad mq = null;
			if (e.isShiftDown()) {
				quadOnDeck = DownQuad.class;
				//mq = new DownQuad(bools);
			} 
			Object[] args = new Object[1];
			args[0] = bools;
			
			try {
				mq = quadOnDeck.getConstructor(boolean[][].class).newInstance(args);
				mq.setPosition(row, col);
				quads.add(mq);
			} catch (Exception e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			
		} else {
			
			// flip color
			
			
			
			bools[row][col] = !bools[row][col];
			
			createGridImage();
			
			repaint();
		}
		
		
		
	}
	
	public void setQuadOnDeck(Class<? extends MarchQuad> cls) {
		quadOnDeck = cls;
	}

	public void setImageOnDeck(MarchableImage mi2) {
		miOnDeck = mi2;
		
	}
	
	public int getCols() {
		return cols;
	}
	
	public void setFrame(JFrame f) {
		frame = f;
	}

	

	
}
